package org.shaka.db.sqlbuilder.sql;

import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

public class SqlSelect {
    private List<SqlColumn> columns;
    private List<SqlAsPartial> froms;
    private List<SqlJoin> joins;
    private SqlConditions conditions;
    private List<SqlColumn> groupBy;
    private SqlConditions having;
    private List<SqlColumn> orderBy;

    public SqlSelect() {
        columns = Lists.newArrayList();
        froms = Lists.newArrayList();
        joins = Lists.newArrayList();
        groupBy = Lists.newArrayList();
        orderBy = Lists.newArrayList();
    }

    public SqlSelect select(SqlColumn column) {
        this.columns.add(column);
        return this;
    }

    public SqlSelect select(List<SqlColumn> columns) {
        this.columns.addAll(columns);
        return this;
    }

    public SqlSelect from(SqlAsPartial asPartial) {
        this.froms.add(asPartial);
        return this;
    }

    public SqlSelect join(SqlJoin join) {
        this.joins.add(join);
        return this;
    }

    public SqlSelect where(SqlConditions conditions) {
        if (this.conditions == null) {
            this.conditions = conditions;
        } else {
            this.conditions.addCondition(conditions);
        }
        return this;
    }

    public SqlSelect groupBy(SqlColumn... columns) {
        for (SqlColumn sqlColumn : columns) {
            this.groupBy.add(sqlColumn);
        }
        return this;
    }

    public SqlSelect groupBy(List<SqlColumn> columns) {
        this.groupBy.addAll(columns);
        return this;
    }

    public SqlSelect having(SqlConditions conditions) {
        this.having = conditions;
        return this;
    }

    public SqlSelect order(SqlColumn... columns) {
        for (SqlColumn sqlColumn : columns) {
            this.orderBy.add(sqlColumn);
        }
        return this;
    }

    public SqlSelect order(List<SqlColumn> columns) {
        this.orderBy.addAll(columns);
        return this;
    }

    public String buildSql() {
        StringBuilder sb = new StringBuilder();
        buildSql(sb);
        return sb.toString();
    }

    public boolean hasParams() {
        for (SqlJoin join : this.joins) {
            if (join.hasParams())
                return true;
        }
        for (SqlAsPartial asPartial : this.froms) {
            if (asPartial.hasParams())
                return true;
        }
        if (conditions != null && conditions.hasParams())
            return true;
        if (having != null && having.hasParams())
            return true;
        return false;
    }

    public List<Object> params() {
        List<Object> params = Lists.newArrayList();
        for (SqlJoin join : this.joins) {
            if (join.hasParams())
                params.addAll(join.params());
        }
        for (SqlAsPartial asPartial : this.froms) {
            if (asPartial.hasParams())
                params.addAll(asPartial.params());
        }
        if (conditions != null)
            params.addAll(conditions.getParams());
        if (having != null)
            params.addAll(having.getParams());
        return params;
    }

    public void buildSql(StringBuilder sb) {
        sb.append("SELECT ");
        sb.append(StringUtils.join(Iterators.transform(columns.iterator(), new Function<SqlColumn, String>() {
            @Override
            public String apply(SqlColumn input) {
                return input.selectColumnPartial();
            }
        }), ", "));
        sb.append(" FROM ");
        sb.append(StringUtils.join(Iterators.transform(froms.iterator(), new Function<SqlAsPartial, String>() {
            @Override
            public String apply(SqlAsPartial input) {
                return input.asPartial();
            }
        }), ", "));
        for (SqlJoin join : joins) {
            join.joinPartial(sb);
        }
        if (conditions != null) {
            sb.append(" WHERE ");
            conditions.conditionsPartial(sb);
        }
        if (!groupBy.isEmpty()) {
            sb.append(" GROUP BY ");
            sb.append(StringUtils.join(Iterators.transform(groupBy.iterator(), new Function<SqlColumn, String>() {
                @Override
                public String apply(SqlColumn input) {
                    return input.orderPartial();
                }
            }), ", "));
        }
        if (having != null) {
            sb.append(" HAVING ");
            having.conditionsPartial(sb);
        }
        if (!orderBy.isEmpty()) {
            sb.append(" ORDER ");
            sb.append(StringUtils.join(Iterators.transform(columns.iterator(), new Function<SqlColumn, String>() {
                @Override
                public String apply(SqlColumn input) {
                    return input.orderPartial();
                }
            }), ", "));
        }
    }
}
